package net.gdface.jmx;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.UndeclaredThrowableException;
import java.rmi.ConnectException;
import java.rmi.RemoteException;

import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import javax.management.ReflectionException;

import net.gdface.jmx.exception.MXBeanConnectException;
import net.gdface.jmx.exception.MXBeanRuntimeException;
import net.gdface.utils.BaseInterfaceDecorator;
import static net.gdface.utils.ConditionChecks.checkState;

/**
 * MBean接口封装基类<br>
 * 
 * @author guyadong
 *
 * @param <I> 接口类型
 */
public abstract class BaseMBeanDelegate<I>{
	private volatile I remoteMBean;
	protected final Class<I> mbeanClass;
	private Integer port;
	@SuppressWarnings("unchecked")
	protected BaseMBeanDelegate() {
		Type superClass = getClass().getGenericSuperclass();
		this.mbeanClass = (Class<I>) getRawClass(((ParameterizedType) superClass).getActualTypeArguments()[0]);
	}
	protected BaseMBeanDelegate(Class<I> mbeanClass) {
		if(null == mbeanClass){
			throw new NullPointerException("mbeanClass is null");
		}
		this.mbeanClass = mbeanClass;
	}
    private static Class<?> getRawClass(Type type){
        if(type instanceof Class<?>){
            return (Class<?>) type;
        } else if(type instanceof ParameterizedType){
            return getRawClass(((ParameterizedType) type).getRawType());
        } else{
            throw new IllegalArgumentException("invalid type");
        }
    }
	protected abstract Object getMBean();

	/**
	 * 将当前实例注册到 MBeanServer
	 * @param port Registry  port,use 1099 if null,no start JMX server if {@code < 0}
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws IOException
	 */
	public void registerMBean(Integer port) throws InstanceAlreadyExistsException, 
		MBeanRegistrationException, 
		IOException{
		try {
			Object mbean = getMBean();
			if(mbean == null){
				throw new NullPointerException("mbean is null");
			}
			JMXSupport.registerMBean(mbean,mbeanClass, port);
		} catch (NotCompliantMBeanException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 将当前实例注册到 MBeanServer<br>
	 * 该方法不会启动JMX server
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @see #registerMBean(Integer)
	 */
	public void registerMBeanOnly()
			throws InstanceAlreadyExistsException, MBeanRegistrationException {
		try {
			Object mbean = getMBean();
			if(mbean == null){
				throw new NullPointerException("mbean is null");
			}
			JMXSupport.registerMBeanOnly(mbean,mbeanClass);
		} catch (NotCompliantMBeanException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * 将当前实例注册到 MBeanServer,并启动JMX server,
	 * 端口必须为{@link #setPort(Integer)}指定
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws IOException、
	 * @see #registerMBean(Integer)
	 */
	public void registerMBean()
			throws InstanceAlreadyExistsException, MBeanRegistrationException, IOException {
		checkState(null != port,"port is uninitizlied,please call setPort(Integer) firstly");
		registerMBean(port);
	}
	/**
	 * 设置remoteMBean为{@code null}
	 */
	private void resetMBeanInLocalhost(){
		remoteMBean = null;
	}
	/**
	 * 获取接口实例
	 * @param port Registry  port,use 1099 if null
	 * @return 接口实例
	 * @throws IOException
	 * @throws InstanceNotFoundException 
	 */
	public I getMBeanInLocalhostChecked(Integer port) throws IOException, InstanceNotFoundException    {
		if(remoteMBean == null){
			synchronized (this) {
				if(remoteMBean == null){
					try {
						remoteMBean = JMXSupport.getMBeanInLocalhost(mbeanClass,port);
					} catch (NotCompliantMBeanException e) {
						throw new RuntimeException(e);
					}
				}
			}
		}
		return remoteMBean;
	}
	/**
	 * 获取接口实例,端口必须为{@link #setPort(Integer)}指定
	 * @return 接口实例
	 * @throws IOException
	 * @throws InstanceNotFoundException 
	 * @see #getMBeanInLocalhostChecked(Integer)
	 */
	public I getMBeanInLocalhostChecked() throws IOException, InstanceNotFoundException    {
		checkState(null != port,"port is uninitizlied,please call setPort(Integer) firstly");
		return getMBeanInLocalhostChecked(port);
	}
	/**
	 * 获取智能接口实例,参见{@code MXBeanDecorator}
	 * @param port Registry  port,use 1099 if null
	 * @return 接口实例
	 */
	public I getIntelligentMBeanInLocalhost(Integer port)    {
		return new MXBeanDecorator(port).proxyInstance();
	}
	/**
	 * 获取智能接口实例,参见{@code MXBeanDecorator},端口必须为{@link #setPort(Integer)}指定,
	 * @return 接口实例
	 * @see #getIntelligentMBeanInLocalhost(Integer)
	 */
	public I getIntelligentMBeanInLocalhost()     {
		checkState(null != port,"port is uninitizlied,please call setPort(Integer) firstly");
		return getIntelligentMBeanInLocalhost(port);
	}
	public Integer getPort() {
		return port;
	}
	/**
	 * 指定JMX端口
	 * @param port
	 * @return 当前对象
	 */
	public BaseMBeanDelegate<I> setPort(Integer port) {
		this.port = port;
		return this;
	}
	public Class<I> getMbeanClass() {
		return mbeanClass;
	}
	
	/**
	 * MXBean接口实例代理<br>
	 * 抛出的{@link IOException}封装为{@link MXBeanConnectException}抛出异常并自动调用{@link #resetMBeanInLocalhost()}复位 remoteMBean,
	 * 抛出的{@link RemoteException},{@link InstanceNotFoundException}封装为{@link MXBeanRuntimeException}抛出异常
	 * @author guyadong
	 *
	 */
	private class MXBeanDecorator extends BaseInterfaceDecorator<I,I>{
		private Integer port;
		MXBeanDecorator(Integer port) {
			/** delegate 字段用不上,初始化为null */
			super(mbeanClass, null);
			this.port = port;
		}

		@Override
		protected Object doInvoke(Object proxy, Method method, Object[] args) throws Throwable {
			I m;
			try {
				m = getMBeanInLocalhostChecked(port);
			} catch ( IOException e) {
				throw new InvocationTargetException(new MXBeanConnectException(e));
			}catch (InstanceNotFoundException e) {
				throw new InvocationTargetException(new MXBeanRuntimeException(e));
			}catch (Throwable  e) {
				throw new InvocationTargetException(new MXBeanRuntimeException(e));
			}
			try{
				return method.invoke(m, args);
			}catch (InvocationTargetException e) {
				Throwable target = e.getTargetException();
				/**
				 * 处理MXBean实例抛出的运行时异常<br>
				 * 从{@link UndeclaredThrowableException}中分离JMX相关异常
				 * 当实例连接异常时抛出{@link MXBeanConnectException}
				 */
				if(target instanceof UndeclaredThrowableException){
					try{
						throw target.getCause();
					}catch (ConnectException e1) {
						resetMBeanInLocalhost();
						throw new InvocationTargetException(new MXBeanConnectException(e1));
					}catch (RemoteException | InstanceNotFoundException | ReflectionException e1) {
						/** see MBeanServerInvocationHandler.invoke(Object proxy, Method method, Object[] args) */
						throw new InvocationTargetException(new MXBeanRuntimeException(e1));
					}catch (Throwable e1) {
						throw e;
					}
				}
				throw e;
			}
		}		
	}
}


